|
1
|
|
|
'use strict'; |
|
2
|
|
|
|
|
3
|
1 |
|
var BigInteger = require('bigi'); |
|
4
|
1 |
|
var ALPHABET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'; |
|
5
|
|
|
|
|
6
|
|
|
// pre-compute lookup table |
|
7
|
1 |
|
var SEPARATOR = ':'; |
|
8
|
1 |
|
var CSLEN = 8; |
|
9
|
1 |
|
var ALPHABET_MAP = {}; |
|
10
|
1 |
|
for (var z = 0; z < ALPHABET.length; z++) { |
|
11
|
32 |
|
var x = ALPHABET.charAt(z); |
|
12
|
32 |
|
if (ALPHABET_MAP[x] !== undefined) { |
|
13
|
|
|
throw new TypeError(x + ' is ambiguous'); |
|
14
|
|
|
} |
|
15
|
32 |
|
ALPHABET_MAP[x] = z; |
|
16
|
|
|
} |
|
17
|
|
|
|
|
18
|
|
|
function polymodStep(pre) { |
|
19
|
1382 |
|
var b = pre.shiftRight(35); |
|
20
|
1382 |
|
var mask = BigInteger.fromHex('07ffffffff'); |
|
21
|
|
|
|
|
22
|
1382 |
|
var v = pre.and(mask).shiftLeft(new BigInteger('5')); |
|
23
|
|
|
|
|
24
|
1382 |
|
if (b.and(new BigInteger('1')).intValue() > 0) { |
|
25
|
611 |
|
v = v.xor(BigInteger.fromHex('98f2bc8e61')); |
|
26
|
|
|
} |
|
27
|
1382 |
|
if (b.and(new BigInteger('2')).intValue()) { |
|
28
|
591 |
|
v = v.xor(BigInteger.fromHex('79b76d99e2')); |
|
29
|
|
|
} |
|
30
|
1382 |
|
if (b.and(new BigInteger('4')).intValue()) { |
|
31
|
576 |
|
v = v.xor(BigInteger.fromHex('f33e5fb3c4')); |
|
32
|
|
|
} |
|
33
|
1382 |
|
if (b.and(new BigInteger('8')).intValue()) { |
|
34
|
643 |
|
v = v.xor(BigInteger.fromHex('ae2eabe2a8')); |
|
35
|
|
|
} |
|
36
|
1382 |
|
if (b.and(new BigInteger('16')).intValue()) { |
|
37
|
652 |
|
v = v.xor(BigInteger.fromHex('1e4f43e470')); |
|
38
|
|
|
} |
|
39
|
|
|
|
|
40
|
1382 |
|
return v; |
|
41
|
|
|
} |
|
42
|
|
|
|
|
43
|
|
|
function prefixChk(prefix) { |
|
44
|
27 |
|
var chk = new BigInteger('1'); |
|
45
|
27 |
|
for (var i = 0; i < prefix.length; ++i) { |
|
46
|
270 |
|
var c = prefix.charCodeAt(i); |
|
47
|
|
|
|
|
48
|
270 |
|
var mixwith = new BigInteger('' + (c & 0x1f)); |
|
49
|
270 |
|
chk = polymodStep(chk).xor(mixwith); |
|
50
|
|
|
} |
|
51
|
|
|
|
|
52
|
27 |
|
chk = polymodStep(chk); |
|
53
|
27 |
|
return chk; |
|
54
|
|
|
} |
|
55
|
|
|
|
|
56
|
|
|
function encode(prefix, words) { |
|
57
|
|
|
// too long? |
|
58
|
10 |
|
if (prefix.length + CSLEN + 1 + words.length > 90) { |
|
59
|
1 |
|
throw new TypeError('Exceeds Base32 maximum length'); |
|
60
|
|
|
} |
|
61
|
|
|
|
|
62
|
9 |
|
prefix = prefix.toLowerCase(); |
|
63
|
|
|
|
|
64
|
|
|
// determine chk mod |
|
65
|
9 |
|
var chk = prefixChk(prefix); |
|
66
|
9 |
|
var result = prefix + SEPARATOR; |
|
67
|
9 |
|
for (var i = 0; i < words.length; ++i) { |
|
68
|
275 |
|
var _x = words[i]; |
|
69
|
275 |
|
if (_x >>> 5 !== 0) { |
|
70
|
1 |
|
throw new Error('Non 5-bit word'); |
|
71
|
|
|
} |
|
72
|
|
|
|
|
73
|
274 |
|
chk = polymodStep(chk).xor(new BigInteger('' + _x)); |
|
74
|
274 |
|
result += ALPHABET.charAt(_x); |
|
75
|
|
|
} |
|
76
|
|
|
|
|
77
|
8 |
|
for (var _i = 0; _i < CSLEN; ++_i) { |
|
78
|
64 |
|
chk = polymodStep(chk); |
|
79
|
|
|
} |
|
80
|
8 |
|
chk = chk.xor(new BigInteger('1')); |
|
81
|
8 |
|
for (var _i2 = 0; _i2 < CSLEN; ++_i2) { |
|
82
|
64 |
|
var pos = 5 * (CSLEN - 1 - _i2); |
|
83
|
64 |
|
var v2 = chk.shiftRight(new BigInteger('' + pos)).and(BigInteger.fromHex('1f')); |
|
84
|
64 |
|
result += ALPHABET.charAt(v2.toString(10)); |
|
85
|
|
|
} |
|
86
|
|
|
|
|
87
|
8 |
|
return result; |
|
88
|
|
|
} |
|
89
|
|
|
|
|
90
|
|
|
function decode(str) { |
|
91
|
24 |
|
if (str.length < 8) { |
|
92
|
1 |
|
throw new TypeError(str + ' too short'); |
|
93
|
|
|
} |
|
94
|
23 |
|
if (str.length > 90) { |
|
95
|
1 |
|
throw new TypeError(str + ' too long'); |
|
96
|
|
|
} |
|
97
|
|
|
|
|
98
|
|
|
// don't allow mixed case |
|
99
|
22 |
|
var lowered = str.toLowerCase(); |
|
100
|
22 |
|
var uppered = str.toUpperCase(); |
|
101
|
22 |
|
if (str !== lowered && str !== uppered) { |
|
102
|
1 |
|
throw new Error('Mixed-case string ' + str); |
|
103
|
|
|
} |
|
104
|
|
|
|
|
105
|
21 |
|
str = lowered; |
|
106
|
|
|
|
|
107
|
21 |
|
var split = str.lastIndexOf(SEPARATOR); |
|
108
|
21 |
|
if (split === -1) { |
|
109
|
1 |
|
throw new Error('No separator character for ' + str); |
|
110
|
|
|
} |
|
111
|
|
|
|
|
112
|
20 |
|
if (split === 0) { |
|
113
|
1 |
|
throw new Error('Missing prefix for ' + str); |
|
114
|
|
|
} |
|
115
|
|
|
|
|
116
|
19 |
|
var prefix = str.slice(0, split); |
|
117
|
19 |
|
var wordChars = str.slice(split + 1); |
|
118
|
19 |
|
if (wordChars.length < 6) { |
|
119
|
1 |
|
throw new Error('Data too short'); |
|
120
|
|
|
} |
|
121
|
|
|
|
|
122
|
18 |
|
var chk = prefixChk(prefix); |
|
123
|
18 |
|
var words = []; |
|
124
|
18 |
|
for (var i = 0; i < wordChars.length; ++i) { |
|
125
|
748 |
|
var c = wordChars.charAt(i); |
|
126
|
748 |
|
var v = ALPHABET_MAP[c]; |
|
127
|
748 |
|
if (v === undefined) { |
|
128
|
1 |
|
throw new Error('Unknown character ' + c); |
|
129
|
|
|
} |
|
130
|
|
|
|
|
131
|
747 |
|
chk = polymodStep(chk).xor(new BigInteger('' + v)); |
|
132
|
|
|
// not in the checksum? |
|
133
|
747 |
|
if (i + CSLEN >= wordChars.length) { |
|
134
|
136 |
|
continue; |
|
135
|
|
|
} |
|
136
|
611 |
|
words.push(v); |
|
137
|
|
|
} |
|
138
|
|
|
|
|
139
|
17 |
|
if (chk.toString(10) !== '1') { |
|
140
|
9 |
|
throw new Error('Invalid checksum for ' + str); |
|
141
|
|
|
} |
|
142
|
|
|
|
|
143
|
8 |
|
return { prefix: prefix, words: words }; |
|
144
|
|
|
} |
|
145
|
|
|
|
|
146
|
|
|
function convert(data, inBits, outBits, pad) { |
|
147
|
18 |
|
var value = 0; |
|
148
|
18 |
|
var bits = 0; |
|
149
|
18 |
|
var maxV = (1 << outBits) - 1; |
|
150
|
|
|
|
|
151
|
18 |
|
var result = []; |
|
152
|
18 |
|
for (var i = 0; i < data.length; ++i) { |
|
153
|
514 |
|
value = value << inBits | data[i]; |
|
154
|
514 |
|
bits += inBits; |
|
155
|
|
|
|
|
156
|
514 |
|
while (bits >= outBits) { |
|
157
|
477 |
|
bits -= outBits; |
|
158
|
477 |
|
result.push(value >>> bits & maxV); |
|
159
|
|
|
} |
|
160
|
|
|
} |
|
161
|
|
|
|
|
162
|
18 |
|
if (pad) { |
|
163
|
8 |
|
if (bits > 0) { |
|
164
|
8 |
|
result.push(value << outBits - bits & maxV); |
|
165
|
|
|
} |
|
166
|
|
|
} else { |
|
167
|
10 |
|
if (bits >= inBits) { |
|
168
|
1 |
|
throw new Error('Excess padding'); |
|
169
|
|
|
} |
|
170
|
9 |
|
if (value << outBits - bits & maxV) { |
|
171
|
1 |
|
throw new Error('Non-zero padding'); |
|
172
|
|
|
} |
|
173
|
|
|
} |
|
174
|
|
|
|
|
175
|
16 |
|
return result; |
|
176
|
|
|
} |
|
177
|
|
|
|
|
178
|
|
|
function toWords(bytes) { |
|
179
|
8 |
|
return convert(bytes, 8, 5, true); |
|
180
|
|
|
} |
|
181
|
|
|
|
|
182
|
|
|
function fromWords(words) { |
|
183
|
10 |
|
return convert(words, 5, 8, false); |
|
184
|
|
|
} |
|
185
|
|
|
|
|
186
|
|
|
module.exports = { decode: decode, encode: encode, toWords: toWords, fromWords: fromWords }; |